package CACA;

import java.util.ArrayList;
import java.util.Iterator;

import DataSetPackage.Attribute;
import DataSetPackage.DataSet;

public class CACAClassifier {
	private String logString ;
	//DataSet data;
	ItemColumn[][] vertivalData;
	ORTree orTree;
	int noInstances;
	int classIndex=-1;
	int minSuppCount;
	double minConf;
	double[] weight;
	ArrayList<Attribute> attributes;


	
	public CACAClassifier() 
	{
	}
	public int getNoInstances() {
		return noInstances;
	}


	public void setClassIndex(int classIndex) {
		this.classIndex = classIndex;
		weight = null;
	}

	public int getClassIndex() {
		return classIndex;
	}

	public ORTree getOrTree() {
		return orTree;
	}

	public Attribute getClassAttribute()
	{
		return attributes.get(classIndex);
	}
	public void train(DataSet data, double minSupp, double minConf, boolean usePercent) {
		
		orTree = new ORTree();
		orTree.weight = weight;
		logString = "";
		
		attributes = data.getAttributes();
		
		noInstances = data.getNumberOfIntances();
		if(classIndex == -1)
		{
			classIndex = data.getNumberOfAttributes()-1;
		}
		if(usePercent)
		{
			minSuppCount = noInstances - (int) ((1 - minSupp/100) * noInstances);
			System.out.print(' '); System.out.print(minSuppCount); System.out.print(' ');
		}
		else
		{
			this.minSuppCount = (int)minSupp;
		}
		this.minConf = minConf/100;
		
		// step1
		vertivalData = getVerticalRepresentation(data);
		// step2
		ArrayList<ArrayList<ItemColumn>> singleItemSets = classDivide(vertivalData, minSuppCount);
		// step3
		int i=0;
		for (Iterator<ArrayList<ItemColumn>> iterator = singleItemSets.iterator(); iterator.hasNext();i++) {
			ArrayList<ItemColumn> singleItemSet = (ArrayList<ItemColumn>) iterator.next();
			while(!singleItemSet.isEmpty())
			{
				ItemColumn itemCol = singleItemSet.remove(0);
	
				ItemSet itemset = new ItemSet();
				itemset.items.add(itemCol.item);
				itemset.suppCount = itemCol.instances.size();
				
				ArrayList<Integer> insct =  itemCol.intersect(vertivalData[classIndex][i]);
				Rule rule = new Rule(itemset, i);
				rule.suppCount = insct.size();
				rule.calculateConf();
				if( rule.getConf() >= minConf )
				{
					orTree.insert(rule);

				}
				if(!isPrunable(rule))
				{
					//step 4
					miningRule( rule,itemCol.instances,singleItemSet);
				}
			}
		}
		Rule defaultRule = getDefaultRule();
		orTree.insert(defaultRule);
		//orTree.compact();
	}
	public void setWeight(double[]weight)
	{
		this.weight = weight;
	}
	public double[] getWeight()
	{
		return orTree.weight;
	}
	
	private Rule getDefaultRule() {
		int defaultLabel = 0;
		int maxCount = vertivalData[classIndex][0].count();
		for(int i=1;i<vertivalData[classIndex].length;i++)
		{
			int count = vertivalData[classIndex][i].count();
			if(count>maxCount)
			{
				defaultLabel = i;
				maxCount = count;
			}
		}
		ItemSet itemSet = new ItemSet();
		itemSet.suppCount = 1;
		Rule defaultRule = new Rule(itemSet, defaultLabel);
		defaultRule.suppCount = 0;
		return defaultRule;
	}
	
	public boolean isPrunable(Rule rule)
	{
		double conf = rule.getConf();
		double s = (double)(rule.suppCount)*(1-conf)/conf;
		return s<minSuppCount;
	}

	public void miningRule(Rule rule,ArrayList<Integer> instances,ArrayList<ItemColumn> singleItemSet)
	{
		ArrayList<ItemColumn> newSingleItemSet = new ArrayList<ItemColumn>();
		for (ItemColumn itemInstancesColumn : singleItemSet) {
			if(itemInstancesColumn.item.attribute != rule.itemSet.getLast().attribute)
			{
				newSingleItemSet.add(itemInstancesColumn);
			}
		}
		while(!newSingleItemSet.isEmpty())
		{
			ItemColumn itemCol = newSingleItemSet.remove(0);
			
			ArrayList<Integer> insct =  itemCol.intersect(instances);
			
			ItemSet itemset = rule.itemSet.clone();
			itemset.items.add(itemCol.item);
			itemset.suppCount = insct.size();
			Rule r = new Rule(itemset, rule.label);
			r.suppCount = vertivalData[classIndex][rule.label].intersect(insct).size();
			
			//appendLogString("\nmine : "+rule2String(r));
			if(r.suppCount >= minSuppCount)
			{
				r.calculateConf();
				//appendLogString("+");
				if(r.getConf()>=minConf)
				{
					orTree.insert(r);
				}
				if(!isPrunable(rule))
				{
					miningRule(r,insct,newSingleItemSet);
				}	
				else
				{
					//appendLogString("-");
				}
			}
		}
	}
	public ModelTestInfo test(DataSet data) {

		ModelTestInfo info = new ModelTestInfo(getClassAttribute().getNumberOfNominal());
		for (Iterator<int[]> iterator = data.getDataIterator(); iterator.hasNext();) {
			int[] row = (int[]) iterator.next();
			int label = orTree.predict(row);
			int accLabel = row[classIndex];
			if(label == -1)
				label = 0;
			info.confusionMatrix[accLabel][label] ++;
			if(label == accLabel)
			{
				info.correct++;
			}
			else
			{
				info.incorrect++;
			}
			info.prediction.add(label);
		}
		return info;
	}

	public int classify(int[] row) {
		return -1;
	}

	private ArrayList<ArrayList<ItemColumn>> classDivide(ArrayList<ItemColumn> itemCols, ItemColumn[] labels, int minCount) {
		
		appendLogString( "\n\n===== Single attribute value set =====");
		
		ArrayList<ArrayList<ItemColumn>> singleItemSets = new ArrayList<ArrayList<ItemColumn>>();
		for (int i = 0; i < labels.length; i++) {
			ArrayList<ItemColumn> singleItemSet = new ArrayList<ItemColumn>();
			
			appendLogString( "\nC"+String.valueOf(i) + " :");
			
			for (Iterator<ItemColumn> iterator = itemCols.iterator(); iterator.hasNext();) {
				ItemColumn itemColunm = (ItemColumn) iterator.next();

				if (itemColunm.intersect(labels[i]).size() >= minCount) {
					singleItemSet.add(itemColunm);	
					
					appendLogString (" ("+item2String(itemColunm.item) + ")");
				}
			}
			singleItemSets.add(singleItemSet);
		}
		return singleItemSets;
	}
	private ItemColumn[][] getVerticalRepresentation(DataSet dataset)
	{
		ItemColumn[][] cols = new ItemColumn[dataset.getNumberOfAttributes()][];
		for (int i = 0; i < cols.length; i++) {
			cols[i] = new ItemColumn[dataset.getAttribute(i).getNumberOfNominal()];
			for (int j = 0; j < cols[i].length; j++) {
				cols[i][j] = new ItemColumn(i, j);
			}
		}		
		
		int k = 0;
		for (Iterator<int[]> iterator = dataset.getDataIterator(); iterator.hasNext(); k++) {
			int[] row = (int[]) iterator.next();
			for (int i = 0; i < row.length; i++) {
				if (row[i] != -1)
					cols[i][row[i]].instances.add(k);
			}
		}
		return cols;
	}

	private ArrayList<ArrayList<ItemColumn>> classDivide(ItemColumn[][] cols, int minCount) {
		ArrayList<ItemColumn> itemCols = new ArrayList<ItemColumn>();
		
		for (int i = cols.length-1; i >= 0; i--) {
			for (int j = cols[i].length-1; j >= 0; j--) {
				int count = cols[i][j].count();
				if (count < minCount || i == classIndex) {
					continue;
				}
				int k = 0;
				boolean isInserted = false;
				for (Iterator<ItemColumn> iterator = itemCols.iterator(); iterator
						.hasNext(); k++) {
					ItemColumn item = (ItemColumn) iterator.next();
					if (item.count() <= count) {
						itemCols.add(k, cols[i][j]);
						isInserted = true;
						break;
					}
				}
				if (!isInserted) {
					itemCols.add(cols[i][j]);
				}
			}
		}

		printLog(  itemCols,cols[classIndex]);
		
		return classDivide(itemCols, cols[classIndex], minCount);
	}

	private void printLog(ArrayList<ItemColumn> itemCols,ItemColumn[] cols) {
		appendLogString("===== Vertical representation =====\n");
		for (Iterator<ItemColumn> iterator = itemCols.iterator(); iterator.hasNext();) {
			ItemColumn itemInstancesColumn = (ItemColumn) iterator
					.next();
			appendLogString("\na"+ item2String(itemInstancesColumn.item)+":");
			
			for (Integer i : itemInstancesColumn.instances) {
				appendLogString (" "+ String.valueOf(i+1));
			}
			
		}
		appendLogString("\n");
		for (int i = 0;i<cols.length;i++) {

			appendLogString( ("\nc" + String.valueOf(cols[i].item.value)+":"));
			for (Integer j : cols[i].instances) {
				appendLogString(" "+ String.valueOf(j+1));
			}
			
		}
	}

	public String getLogString() {
		return logString;
	}

	private void appendLogString(String logString) {
		this.logString += logString;
	}
	
	public String rule2String(Rule rule)
	{
		Attribute a = attributes.get(classIndex);
		String str = "";
		boolean first = true;
		for (Item item : rule.itemSet.items) {
			
			if(first)
			{
				first = false;
			}
			else
			{
				str+="^ ";
			}
			str += "("+item2String(item) + ") ";
		}
		str+= "-> " + a.getNominal(rule.label);
		return str;
	}
	public String item2String(Item item)
	{
		Attribute a = attributes.get(item.attribute);
		return a.getName() + " = " + a.getNominal(item.value);
	}
	public String label2String(int label)
	{
		Attribute a = attributes.get(classIndex);
		return a.getName() + " = "+ a.getNominal(label);
	}

	public ArrayList<Attribute> getAttributes() {
		// TODO Auto-generated method stub
		return attributes;
	}

}
